package com.licq.ldoc.utils;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import sun.reflect.generics.reflectiveObjects.WildcardTypeImpl;

import com.licq.ldoc.support.bean.DefaultKeyValue;
import com.licq.ldoc.support.bean.KeyValue;

/**
 * 
 * @author liujian
 *
 */
public abstract class ClassUtils {
	private static transient Log logger = LogFactory.getLog(ClassUtils.class);
	
	private static final Map<Class<?>, Class<?>> primitiveWrapperTypeMap = new HashMap<Class<?>, Class<?>>(8);
	private static final List<Class<?>> basicJavaType = new ArrayList<Class<?>>();
	
	static {
		primitiveWrapperTypeMap.put(Boolean.class, boolean.class);
		primitiveWrapperTypeMap.put(Byte.class, byte.class);
		primitiveWrapperTypeMap.put(Character.class, char.class);
		primitiveWrapperTypeMap.put(Double.class, double.class);
		primitiveWrapperTypeMap.put(Float.class, float.class);
		primitiveWrapperTypeMap.put(Integer.class, int.class);
		primitiveWrapperTypeMap.put(Long.class, long.class);
		primitiveWrapperTypeMap.put(Short.class, short.class);
		
		for (Entry<Class<?>, Class<?>> entry : primitiveWrapperTypeMap.entrySet()) {
			basicJavaType.add(entry.getKey());
			basicJavaType.add(entry.getValue());
		}
		basicJavaType.add(String.class);
		basicJavaType.add(BigDecimal.class);
		basicJavaType.add(BigInteger.class);
		basicJavaType.add(Date.class);
		basicJavaType.add(Calendar.class);
		basicJavaType.add(java.sql.Date.class);
		basicJavaType.add(Timestamp.class);
		basicJavaType.add(Time.class);
		basicJavaType.add(Class.class);
		basicJavaType.add(Object.class);
	}
	
	public static boolean isPrimitiveWrapper(Class<?> clazz) {
		if (clazz == null) {
			throw new IllegalArgumentException("argument clazz can't be null.");
		}
		return primitiveWrapperTypeMap.containsKey(clazz);
	}
	
	public static boolean isBasicJavaType(Class<?> clazz) {
		return basicJavaType.contains(clazz);
	}
	
	public static boolean isEnum(Class<?> clazz) {
		return clazz.isEnum();
	}
	
	public static boolean isBasicJavaTypeWrapper(Field field) {
		Class<?> clazz = field.getType();
		return isWrapper(clazz) && isBasicJavaType(getWrapperNestedType(field));
	}
	
	public static boolean isEnumWrapper(Field field) {
		Class<?> clazz = field.getType();
		return isWrapper(clazz) && isEnum(getWrapperNestedType(field));
	}
	
	/**
	 * field is map class, and a element is enum type at least
	 * @param field
	 * @return
	 */
	public static boolean isEnumMapper(Field field) {
		Class<?> clazz = field.getType();
		if (!isMapper(clazz)) {
			return false;
		}
		KeyValue<Class<?>, Class<?>> nestedType = getMapperNestedType(field);
		return isEnum(nestedType.getKey()) || isEnum(nestedType.getValue());
	}
	
	/**
	 * field is map class, and both element is basic java type
	 * @param field
	 * @return
	 */
	public static boolean isBasicJavaTypeMapper(Field field) {
		Class<?> clazz = field.getType();
		if (!isMapper(clazz)) {
			return false;
		}
		KeyValue<Class<?>, Class<?>> nestedType = getMapperNestedType(field);
		return isBasicJavaType(nestedType.getKey()) && isBasicJavaType(nestedType.getValue());
	}
	
	/**
	 * wrap with collection or array, no consideration of Map
	 * @param clazz
	 * @return
	 */
	public static boolean isWrapper(Class<?> clazz) {
		return Collection.class.isAssignableFrom(clazz) || clazz.isArray();
	}
	
	public static boolean isMapper(Class<?> clazz) {
		return Map.class.isAssignableFrom(clazz);
	}
	
	public static boolean isCollectionWithUnbasicJavaType(Field field) {
    	if (!Collection.class.isAssignableFrom(field.getType())) {
    		return false;
    	}
    	return !isBasicJavaType(getWrapperNestedType(field));
    }
	
	public static boolean isArrayWithUnbasicJavaType(Field field) {
		if (!field.getType().isArray()) {
			return false;
		}
		return !isBasicJavaType(getWrapperNestedType(field));
	}
	
	public static Class<?> getWrapperNestedType(Field field) {
		Class<?> clazz = field.getType();
		if (Collection.class.isAssignableFrom(clazz)) {
			Type genericType = field.getGenericType();
			if (!ParameterizedType.class.isInstance(genericType)) {
				return Object.class;
			} else {
				ParameterizedType parameterizedType = (ParameterizedType) genericType;
				Type actualArgType = parameterizedType.getActualTypeArguments()[0];
				if (Class.class.isInstance(actualArgType)) {
					return (Class<?>) actualArgType;
				} else {
					return (Class<?>) ((WildcardTypeImpl) actualArgType).getUpperBounds()[0];
				}
			}
		} else if (clazz.isArray()) {
			return clazz.getComponentType();
		} else {
			throw new UnsupportedOperationException(String.format(
					"not support field with type[%s].", 
					clazz
				)
			);
		}
	}
	
	public static KeyValue<Class<?>, Class<?>> getMapperNestedType(Field field) {
		Class<?> clazz = field.getType();
		if (isMapper(clazz)) {
			Type genericType = field.getGenericType();
			if (!ParameterizedType.class.isInstance(genericType)) {
				return new DefaultKeyValue<Class<?>, Class<?>>(Object.class, Object.class);
			} else {
				ParameterizedType parameterizedType = (ParameterizedType) genericType;
				Type actualKeyArgType = parameterizedType.getActualTypeArguments()[0];
				Type actualValueArgType = parameterizedType.getActualTypeArguments()[1];
				Class<?> actualKeyArgClazz = null;
				Class<?> actualValueArgClazz = null;
				if (Class.class.isInstance(actualKeyArgType)) {
					actualKeyArgClazz = (Class<?>) actualKeyArgType;
				} else {
					actualKeyArgClazz = (Class<?>) ((WildcardTypeImpl) actualKeyArgType).getUpperBounds()[0];
				}
				if (Class.class.isInstance(actualValueArgType)) {
					actualValueArgClazz = (Class<?>) actualValueArgType;
				} else {
					actualValueArgClazz = (Class<?>) ((WildcardTypeImpl) actualValueArgType).getUpperBounds()[0];
				}
				return new DefaultKeyValue<Class<?>, Class<?>>(actualKeyArgClazz, actualValueArgClazz);
			}
		} else {
			throw new UnsupportedOperationException(String.format(
					"not support field with type[%s] of class[%s] declared.", 
					clazz,
					field.getDeclaringClass().getName()));
		}
	}
	
	public static boolean isAbstractClass(Class<?> clazz) {
		return Modifier.isAbstract(clazz.getModifiers());
	}
	
	/**
	 * 返回指定类及其所有自定义父类声明的Field(不包含系统)
	 * @param clazz
	 * @return
	 */
	public static Collection<Field> getAllDeclaredFields(Class<?> clazz, Class<?> utilClazz,
			boolean excludeStaticAndFinal) {
		Collection<Field> fields = new ArrayList<Field>();
		for (Field field : clazz.getDeclaredFields()) {
			int modifiers = field.getModifiers();
			if (excludeStaticAndFinal && (Modifier.isStatic(modifiers) || Modifier.isFinal(modifiers))) {
				continue;
			}
			fields.add(field);
		}
		Class<?> superclazz = clazz.getSuperclass();
		if (!isBasicJavaType(superclazz) && superclazz != utilClazz) {
			fields.addAll(getAllDeclaredFields(superclazz, utilClazz, excludeStaticAndFinal));
		}
		return fields;
	}
	
	public static ClassLoader getDefaultClassLoader() {
		ClassLoader cl = null;
		try {
			cl = Thread.currentThread().getContextClassLoader();
		} catch (Throwable ex) {
			logger.debug("Cannot access thread context ClassLoader - falling back to system class loader", ex);
		}
		if (cl == null) {
			cl = ClassUtils.class.getClassLoader();
		}
		return cl;
	}
	
	public static <T> T newInstance(Class<T> clazz) {
		if (clazz == null) {
			throw new IllegalArgumentException("clazz cannot be null.");
		}
		try {
			return clazz.newInstance();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
	
	public static <T> T safeNewInstance(Class<T> clazz) {
		try {
			return newInstance(clazz);
		} catch (Exception e) {
			return null;
		}
	}

}
